k8s 部署mihomo问题记录
k8s 部署 mihomo 问题记录
来源信息
- 标题:Talos 单节点 K8s 部署 Mihomo,并接入已有 Blocky
- 作者:
- 链接:
<local-project-path> - 时间:2026-05-09
一句话摘要
在已有 Talos 单节点 K8s 和 Blocky 的环境中,用 Mihomo Pod 替代 Clash VM / smartdns:Blocky 继续做主 DNS,代理域名转发到 Mihomo DNS,Mihomo 返回 fake-ip,再通过 RouterOS fake-ip 路由把流量送回 K8s 节点上的 Mihomo TUN。
项目当前结构
migraine_mihomo/
├── README.md
├── talos-k8s-mihomo-blocky-existing-guide.md
├── docs/
│ ├── pitfalls.md
│ └── troubleshooting-sop.md
├── k8s/mihomo/
│ ├── namespace.yaml
│ ├── secret.yaml
│ ├── configmap.yaml
│ ├── deployment.yaml
│ └── service.yaml
└── scripts/
├── custom-domains.txt
└── update-blocky-conditional.sh
目标链路
客户端
↓ DNS
Blocky <main-dns-ip>:53
├─ 国内 / LAN / K8s 域名 → Blocky 原上游
└─ 代理域名 → Mihomo DNS <mihomo-dns-ip>:1053
↓
fake-ip 198.18.0.0/16
↓
RouterOS 静态路由
↓
Talos 节点 <talos-node-ip>
↓
Mihomo TUN
↓
代理出口
核心原则:
- Blocky 仍是客户端唯一主 DNS。
- Mihomo 只处理代理域名 DNS、fake-ip、TUN 和代理转发。
- RouterOS 只需要维护
198.18.0.0/16 -> <talos-node-ip>的 fake-ip 路由。 - Talos 宿主机不做手工改动,全部通过 K8s manifest 和 RouterOS 配置完成。
当前配置快照
| 项目 | 当前值 | 来源 |
|---|---|---|
| K8s namespace | network |
k8s/mihomo/namespace.yaml |
| Blocky DNS | <main-dns-ip>:53 |
design / deployment DNS config |
| Mihomo LoadBalancer IP | <mihomo-service-ip> |
k8s/mihomo/service.yaml |
| Talos Node IP | <talos-node-ip> |
design / RouterOS 路由 |
| NFS Server | <nfs-server-ip> |
k8s/mihomo/deployment.yaml |
| NFS path | <nfs-appdata-path> |
k8s/mihomo/deployment.yaml |
| Mihomo image | metacubex/mihomo:v1.19.24 |
k8s/mihomo/deployment.yaml |
| Mihomo DNS | <mihomo-dns-ip>:1053 |
k8s/mihomo/service.yaml |
| mixed-port | <mihomo-service-ip>:7890 |
k8s/mihomo/service.yaml |
| fake-ip range | 198.18.0.1/16 |
k8s/mihomo/configmap.yaml |
部署顺序
项目 README 给出的顺序是:
kubectl apply -f namespace.yaml
kubectl apply -f secret.yaml
kubectl apply -f configmap.yaml
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
部署前确认:
- MetalLB 地址池包含
<mihomo-service-ip>。 - NAS 上存在
<nfs-mihomo-path>。 - Talos 节点存在
/dev/net/tun。 networknamespace 允许 privileged Pod。- 不要把真实订阅 URL 和 controller secret 发布到公开文档。
关键 manifest 设计
Namespace
network namespace 加了 Pod Security privileged label:
pod-security.kubernetes.io/enforce: privileged
pod-security.kubernetes.io/audit: privileged
pod-security.kubernetes.io/warn: privileged
Mihomo 需要 privileged: true 和 /dev/net/tun,否则 TUN 无法创建。
Deployment
关键点:
hostNetwork: true:Mihomo 直接使用节点网络栈。dnsPolicy: None,nameserver 指向 Blocky<main-dns-ip>。strategy: Recreate:避免 rollout 时新旧 Pod 在 hostNetwork 下抢同一组端口。- initContainer 和主容器使用同一个镜像
metacubex/mihomo:v1.19.24,避免 initContainer 额外拉镜像时被 TUN/DNS 问题卡死。 - ConfigMap 只作为模板挂载到
/config-template,initContainer 复制到 NFS 可写目录/data/config.yaml,主容器再把 NFS 子目录挂到/root/.config/mihomo。
ConfigMap
核心配置:
- DNS 监听
0.0.0.0:1053。 - DNS 模式为
fake-ip。 fake-ip-filter排除 LAN、local、K8s 内部域名、NTP、代理节点域名和 registry 域名。nameserver-policy把 K8s 内部域名交给 Blocky,把订阅域名、代理节点域名、registry 域名交给国内 UDP DNS。fallback包含223.5.5.5和119.29.29.29,降低启动阶段 DoH 不可用带来的订阅拉取失败概率。tun.route-exclude-address排除 K8s Pod 网段、Service 网段、局域网网段,以及 Blocky 使用的上游 DNS IP。
当前排除网段:
route-exclude-address:
- <pod-cidr>
- <service-cidr>
- <lan-cidr>
- 223.5.5.5/32
- 223.6.6.6/32
- 114.114.114.114/32
Service
Service 使用 LoadBalancer,固定 IP 为 <mihomo-service-ip>,暴露:
- DNS UDP/TCP:
1053 - mixed-port:
7890 - controller:当前 manifest 为
9090
Blocky 接入方式
Blocky 不整体替换,只更新 conditional mapping:
需要代理的域名 -> <mihomo-dns-ip>:1053
国内 / LAN / K8s 域名 -> Blocky 原有上游
项目里提供了 scripts/update-blocky-conditional.sh:
- 从 GFW 域名源拉取域名。
- 合并
scripts/custom-domains.txt。 - 更新 Blocky Helm values 的
conditional.mapping。 - 默认 Mihomo DNS 为
<mihomo-dns-ip>:1053。
custom-domains.txt 里维护 GFW 列表未覆盖、但需要走 Mihomo 的域名,例如 claude.ai、anthropic.com、容器镜像仓库和 Talos factory 相关域名。
RouterOS 配置
fake-ip 静态路由:
/ip route add dst-address=198.18.0.0/16 gateway=<talos-node-ip> comment="mihomo fake-ip via talos k8s" distance=1
客户端 DHCP DNS 应继续指向 Blocky:
DNS = <main-dns-ip>
不要把 DHCP DNS 改成 <mihomo-dns-ip>:1053,客户端 DNS 配置没有端口概念,而且 Mihomo 不应成为主 DNS。
Mihomo 重启后,如果 RouterOS 缓存了旧 fake-ip 映射,需要清缓存:
/ip dns cache flush
建议降低 RouterOS DNS 缓存 TTL:
/ip dns set cache-max-ttl=1m
验证清单
Pod 和 Service
kubectl get pods -n network -o wide
kubectl get svc -n network mihomo
kubectl logs -n network deploy/mihomo
预期:
- Pod 为
Running。 - Service
EXTERNAL-IP为<mihomo-service-ip>。 - 日志中没有
parse config error、bind: address already in use、tun create failed。
DNS 链路
dig google.com @<mihomo-dns-ip> -p 1053
dig google.com @<main-dns-ip>
dig baidu.com @<main-dns-ip>
dig kubernetes.default.svc.cluster.local @<main-dns-ip>
预期:
| 查询 | 预期 |
|---|---|
google.com @<mihomo-dns-ip> -p 1053 |
返回 198.18.x.x |
google.com @<main-dns-ip> |
返回 198.18.x.x |
baidu.com @<main-dns-ip> |
返回真实国内 IP |
*.cluster.local @<main-dns-ip> |
返回真实 K8s 内部 IP |
TUN 和出口
kubectl exec -n network deploy/mihomo -- ip link
curl https://ipinfo.io
预期:
- Pod 内能看到 Mihomo 创建的 TUN 相关接口。
- 访问代理域名时,出口 IP 为代理节点。
踩坑记录
1. ConfigMap 只读导致启动失败
现象:Pod 启动后崩溃,日志有写入权限错误。
原因:ConfigMap 直接挂到 /root/.config/mihomo 后目录只读,而 Mihomo 启动时要写 cache.db、providers/ 等文件。
解法:ConfigMap 只做模板;initContainer 复制配置到 NFS 可写目录,主容器把整个工作目录挂到 NFS。
2. initContainer 镜像拉取被 TUN/DNS 问题卡死
现象:initContainer ImagePullBackOff,日志显示 TLS 或 DNS 连接失败。
原因:initContainer 使用未缓存镜像时需要拉取,拉取流量又可能被未完全正常的 TUN/DNS 链路影响。
解法:initContainer 使用和主容器相同的 metacubex/mihomo:v1.19.24,复用本地已缓存镜像。
3. hostNetwork rollout 端口冲突
现象:kubectl rollout restart 后新 Pod CrashLoopBackOff,日志报 bind: address already in use。
原因:hostNetwork: true 下新旧 Pod 共用节点网络栈,默认 RollingUpdate 会先启新 Pod。
解法:Deployment 使用 strategy: Recreate。
4. K8s 内部流量被 TUN 劫持
现象:*.svc.cluster.local 被解析成 fake-ip,集群内部访问异常。
原因:auto-route: true 会把节点上流量导入 TUN,未排除 Pod/Service 网段时会误伤集群内部流量。
解法:
tun.route-exclude-address排除<pod-cidr>和<service-cidr>。nameserver-policy把*.cluster.local和*.svc.cluster.local指向 Blocky。
5. Blocky 上游 DNS 查询被 TUN 劫持
现象:baidu.com 查询 Blocky 返回 198.18.x.x,国内域名被 fake-ip 化。
原因:Blocky 的上游 DNS 请求也在节点网络里,可能被 Mihomo TUN 接管。
解法:route-exclude-address 加入 Blocky 的上游 DNS IP,例如 223.5.5.5/32、223.6.6.6/32、114.114.114.114/32,让 Blocky 上游查询绕开 TUN。<lan-cidr> 可保留为局域网流量保护,但不是这个问题的直接修复点。
6. 代理节点域名被 fake-ip 劫持回环
现象:日志出现 dial Proxy ... dns resolve failed,代理连接失败。
原因:代理节点域名被 Mihomo DNS 解析为 fake-ip,流量重新进入 TUN,形成回环。
解法:代理节点域名要同时加入:
fake-ip-filternameserver-policy- 必要时加入
rules走DIRECT
7. 订阅域名启动时解析失败
现象:启动日志出现 initial proxy provider subscription error。
原因:启动阶段 TUN、DoH、代理组还未完全可用,订阅域名解析或连接失败。
解法:
- 订阅域名写进
nameserver-policy,指向223.5.5.5。 fallback加国内 UDP DNS。proxy-providers.subscription.proxy设置为DIRECT。
8. RouterOS DNS 缓存旧 fake-ip
现象:Mihomo 重启后,客户端仍拿到旧 fake-ip 映射,访问失败。
原因:RouterOS 作为客户端默认 DNS 的中间缓存层,保留了 Mihomo 重启前的 fake-ip 映射。
解法:Mihomo 重启后执行 /ip dns cache flush,并把 RouterOS cache-max-ttl 调低。
新增异常域名处理规则
每次发现新域名被误劫持或解析异常,优先按这个顺序处理:
dig <domain> @<mihomo-dns-ip> -p 1053,确认 Mihomo DNS 返回的是 fake-ip 还是真实 IP。dig <domain> @<main-dns-ip>,确认 Blocky 是否把域名转发给 Mihomo。- 如果这是代理节点、订阅、registry、工具域名,不应 fake-ip 回环,加入
fake-ip-filter。 - 需要启动阶段就可解析的域名,加入
nameserver-policy,通常指向223.5.5.5。 - 需要明确直连的域名,加入
rules的DIRECT。 - 需要走代理的普通域名,加入
scripts/custom-domains.txt,再运行 Blocky conditional 更新脚本。
我的理解
这套方案的关键不是“让所有 DNS 都进 Mihomo”,而是把职责拆清楚:
- Blocky 负责主 DNS、国内/LAN/K8s 解析和域名分流。
- Mihomo 负责需要代理的域名解析成 fake-ip,并用 TUN 接住 fake-ip 流量。
- RouterOS 负责把
198.18.0.0/16送回 Talos 节点。 - NFS 负责给 Mihomo 工作目录提供可写持久化,绕开 ConfigMap 只读问题。
最容易出问题的地方是启动时序和流量回环:Mihomo 自己拉订阅、解析代理节点、K8s 内部访问、Blocky 上游查询,都可能被 TUN 误劫持。所以新增域名时不要只改一个地方,要同时考虑 fake-ip、nameserver-policy、rules 和 Blocky conditional mapping。